package ufrj.safcp.tools.imagecapturing;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ufrj.safcp.util.JavaLibraryPath;

import com.lti.civil.CaptureDeviceInfo;
import com.lti.civil.CaptureException;
import com.lti.civil.CaptureObserver;
import com.lti.civil.CaptureStream;
import com.lti.civil.CaptureSystem;
import com.lti.civil.CaptureSystemFactory;
import com.lti.civil.DefaultCaptureSystemFactorySingleton;
import com.lti.civil.Image;
import com.lti.civil.VideoFormat;
import com.lti.civil.awt.AWTImageConverter;

public class LTICivilImageCapturing implements IImageCapturing {
	
	private static Logger logger = LoggerFactory.getLogger(LTICivilImageCapturing.class);
	
	static {
		String lticivilHome = System.getenv("LTICIVIL_HOME");
		
		if (lticivilHome != null) {
			lticivilHome += "\\native\\win32-x86";
		} else {
			throw new IllegalStateException(
					"A variavel de ambiente LTICIVIL_HOME precisa ser definida para este projeto. "
							+ "Ela deve apontar para a home do LTI-CIVIL. Exemplo: C:\\dev\\pfc\\lti-civil. "
							+ "Reinicie o Eclipse após realizar a configuração.");
		}
		
		try {
			JavaLibraryPath.add(new File(lticivilHome));
		} catch (Exception e) {
			logger.error("Erro ao inicializar LTI-Civil", e);
		}
	}
	
	private static LTICivilImageCapturing instance;
	
	public static synchronized LTICivilImageCapturing getInstance() {
		if (instance == null) {
			instance = new LTICivilImageCapturing();
		}
		return instance;
	}
	
	class MyCaptureObserver implements CaptureObserver {

		public void onError(CaptureStream sender, CaptureException e) {
			logger.error("Sender: " + sender, e);
		}

		public void onNewImage(CaptureStream sender, Image image) {

			try {
				LTICivilImageCapturing.this.sendToReceivers(AWTImageConverter.toBufferedImage(image));
			} catch (Exception e) {
				logger.error("Erro ao converter imagem.", e);
			}
			
		}
	}
	
	protected List<IImageReceiver> receivers = new ArrayList<IImageReceiver>();

	protected CaptureSystem system;
	
	protected int desiredDevice;
	
	protected Dimension desiredDimension;
	
	private LTICivilImageCapturing()  {
		super();
		
		observer = new MyCaptureObserver();
		
		CaptureSystemFactory factory;
		factory = DefaultCaptureSystemFactorySingleton.instance();
		try {
			system = factory.createCaptureSystem();
			system.init();
		} catch (CaptureException e) {
			logger.error("Não foi possível inicializar o sistema de captura", e);
		}
	}
	
	public void sendToReceivers(BufferedImage bufferedImage) {
		for (IImageReceiver receiver : receivers) {
			receiver.receiveImage(bufferedImage);
		}
		lastCapturedImage = bufferedImage; 
	}
	
	private MyCaptureObserver observer;

	private CaptureStream captureStream;

	private BufferedImage lastCapturedImage;
	
	public void addImageReceiver(IImageReceiver receiver) {
		if (desiredDimension == null) {
			throw new IllegalStateException("É necessário inicializar antes de adicionar um receiver.");
		}
		receivers.add(receiver);
		receiver.setSize(desiredDimension);
	}
	
	public void init(int device, Dimension d) {
		this.desiredDevice = device;
		this.desiredDimension = d;
	}

	public void start() {
		try {
			
			@SuppressWarnings("unchecked")
			List<CaptureDeviceInfo> list = system.getCaptureDeviceInfoList();
			
			if (list.size() == 0) {
				logger.error("No camera found.");
				return;
			}
	
			CaptureDeviceInfo info = list.get(this.desiredDevice);
	
			logger.info("Device ID " + this.desiredDevice + ": " + info.getDeviceID());
			logger.info("Description " + this.desiredDevice + ": " + info.getDescription());
	
			captureStream = system.openCaptureDeviceStream(info.getDeviceID());
	
			logger.info("Current format " + videoFormatToString(captureStream.getVideoFormat()));
	
			captureStream.setObserver(observer);
			
			
			logger.info("Available formats:");
			List<VideoFormat> formats = captureStream.enumVideoFormats();
	
			VideoFormat choseFormat = null;
			for (VideoFormat format : formats) {
				logger.info(" " + videoFormatToString(format));
				
				if (desiredDimension.getWidth() == format.getWidth() &&  desiredDimension.getHeight() == format.getHeight()) {
					choseFormat = format; 
				}
			}
			
			if (choseFormat == null) {
				throw new IllegalStateException("Não foi possível achar o formato " + desiredDimension + " no dispositivo " + info);
			}
	
			logger.info("Choosing format: " + videoFormatToString(choseFormat));
			captureStream.setVideoFormat(choseFormat);
	
			captureStream.start();
			logger.info("Started Capturing until stop...");
		} catch (CaptureException e) {
			logger.error("Erro ao capturar imagem.", e);
		}
		
	}

	public void stop() {
		try {
			captureStream.stop();
			captureStream.dispose();
		} catch (Exception e) {
			logger.error("Erro ao parar captura.", e);
		}
		
		captureStream = null;
		
	}
	
	public void destroy() {
		logger.info("disposing system...");
		try {
			system.dispose();
		} catch (Exception e) {
			logger.error("Erro ao finalizar captura.", e);
		}
	}
	
	public String videoFormatToString(VideoFormat f) {
		return "Type=" + formatTypeToString(f.getFormatType()) + " Width=" + f.getWidth() + " Height=" + f.getHeight()
				+ " FPS=" + f.getFPS();
	}

	private String formatTypeToString(int f) {
		switch (f) {
		case VideoFormat.RGB24:
			return "RGB24";
		case VideoFormat.RGB32:
			return "RGB32";
		default:
			return "" + f + " (unknown)";
		}
	}

	public BufferedImage getLastCapturedImage() {
		return lastCapturedImage;
	}

}
